/*
 * Copyright (C) 2018 The Asiainfo-Ability Authors
 *
 *      Licensed under the Apache License, Version 2.0 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing, software
 *      distributed under the License is distributed on an "AS IS" BASIS,
 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *      See the License for the specific language governing permissions and
 *      limitations under the License.
 */

package org.asiainfo.ability.provider.utils;

import org.asiainfo.ability.provider.annotation.AbilityFace;
import org.asiainfo.ability.provider.annotation.ApiProvider;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassScaner {

    public static void main(String[] args) throws Exception {
        List<Class<?>> controlerClasses = scanPackage("org.asiainfo.ability.provider"
                , true, RestController.class);
        controlerClasses.addAll(scanPackage("org.asiainfo.ability.provider"
                , true, AbilityFace.class));
        for(Class clazz:controlerClasses) {
            for(Method method:clazz.getDeclaredMethods()) {
                if(method.isAnnotationPresent(ApiProvider.class)) {
                    //produces = "application/json"
                    RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                    if(requestMapping != null) {
                        String[] produces = requestMapping.produces();
                        if(produces.length == 0 || produces[1].equals("application/json")) {

                        }
                    }
                }
                System.out.println(method.isAnnotationPresent(ApiProvider.class));
            }

        }

    }

    public static List<Class<?>> scanPackage(String pkgName, boolean isRecursive
            , Class<? extends Annotation> annotation) throws IOException {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        // 按文件的形式去查找
        //System.out.println(File.separator);
        String strFile = pkgName.replaceAll("\\.", "/");
        Enumeration<URL> urls = loader.getResources(strFile);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url != null) {
                String protocol = url.getProtocol();
                String pkgPath = url.getPath();
                if ("file".equals(protocol)) {
                    findClassName(classList, pkgName, pkgPath, isRecursive, annotation);
                } else if ("jar".equals(protocol)) {
                    findClassName(classList, pkgName, url, isRecursive, annotation);
                }
            }
        }
        return classList;
    }

    public static void findClassName(List<Class<?>> clazzList, File file, Class<? extends Annotation> annotation)
            throws IOException {
        JarFile jarFile = new JarFile(file);
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String jarEntryName = jarEntry.getName();
            String clazzName = jarEntryName.replace("/", ".");
            int endIndex = clazzName.lastIndexOf(".");
            String prefix = null;
            if (endIndex > 0) {
                String prefix_name = clazzName.substring(0, endIndex);
                clazzName = prefix_name;
                endIndex = prefix_name.lastIndexOf(".");
                if (endIndex > 0) {
                    prefix = prefix_name.substring(0, endIndex);
                }
            }
            if (jarEntryName.endsWith(".class")) {
                addClassName(clazzList, clazzName, annotation);
            }
        }
    }

    public static void findClassName(List<Class<?>> clazzList, String pkgName, URL url
            , boolean isRecursive, Class<? extends Annotation> annotation) throws IOException {
        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
        JarFile jarFile = jarURLConnection.getJarFile();
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String jarEntryName = jarEntry.getName();
            String clazzName = jarEntryName.replace("/", ".");
            int endIndex = clazzName.lastIndexOf(".");
            String prefix = null;
            if (endIndex > 0) {
                String prefix_name = clazzName.substring(0, endIndex);
                clazzName = prefix_name;
                endIndex = prefix_name.lastIndexOf(".");
                if (endIndex > 0) {
                    prefix = prefix_name.substring(0, endIndex);
                }
            }
            if (prefix != null && jarEntryName.endsWith(".class")) {
                if (prefix.equals(pkgName)) {
                    addClassName(clazzList, clazzName, annotation);
                } else if (isRecursive && prefix.startsWith(pkgName)) {
                    addClassName(clazzList, clazzName, annotation);
                }

            }
        }
    }


    public static void findClassName(List<Class<?>> clazzList, String pkgName, String pkgPath
            , boolean isRecursive, Class<? extends Annotation> annotation) {
        if (clazzList == null) {
            return;
        }
        File[] files = filterClassFiles(pkgPath);
        if (files != null) {
            for (File f : files) {
                String fileName = f.getName();
                if (f.isFile()) {

                    String clazzName = getClassName(pkgName, fileName);
                    addClassName(clazzList, clazzName, annotation);
                } else {
                    if (isRecursive) {
                        String subPkgName = pkgName + "." + fileName;
                        String subPkgPath = pkgPath + "/" + fileName;
                        findClassName(clazzList, subPkgName, subPkgPath, isRecursive, annotation);
                    }
                }
            }
        }
    }

    private static File[] filterClassFiles(String pkgPath) {
        if (pkgPath == null) {
            return null;
        }
        return new File(pkgPath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
    }

    private static String getClassName(String pkgName, String fileName) {
        int endIndex = fileName.lastIndexOf(".");
        String clazz = null;
        if (endIndex >= 0) {
            clazz = fileName.substring(0, endIndex);
        }
        String clazzName = null;
        if (clazz != null) {
            clazzName = pkgName + "."
                    + clazz;
        }
        return clazzName;
    }

    private static void addClassName(List<Class<?>> clazzList, String clazzName, Class<? extends Annotation> annotation) {

        if (clazzList != null && clazzName != null) {
            Class<?> clazz = null;
            try {
                //quiet load
                clazz = Class.forName(clazzName, false, Thread.currentThread().getContextClassLoader());
            } catch (Throwable e) {
                e.printStackTrace();
            }
            try {
                if (clazz != null) {
                    if (annotation == null) {
                        clazzList.add(clazz);

                    } else if (clazz.isAnnotationPresent(annotation)) {
                        clazzList.add(clazz);
                    }
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }

        }
    }

}
